/*************************************************************************
	> File Name: macroHandler.hpp
	> Author: H.M. Lau
	> Mail: hm.lau@outlook.com
	> Created Time: 2023/10/11 09:41
 ************************************************************************/

#pragma once

#include <iostream>
#include <string>
#include <unordered_map>
#include <vector>
#include "log.hpp"
#include "file_handler.hpp"

class MacroHandler
{
public:
    MacroHandler ()
    {}

    void defMacro(const std::string& filename = "")
    {
        FileHandler handler;
        handler.open(filename);
        std::string macrostr = handler.read();
        _macroDefine(macrostr);
#ifdef DEBUG_SHOW
        std::cout << "Macro define:" << std::endl;
        for (const auto& e : _macro_map) std::cout << e.first << ":" << e.second << std::endl;
#endif
    }

    std::vector<std::string> expandMacro(const std::string& filename = "") 
    {
        FileHandler handler;
        handler.open(filename);
        std::string regular = handler.read();
        if (regular.empty()) {
#ifdef DEBUG_SHOW
            std::cout << "expression is empty." << std::endl;
#endif
            exit(1);
        }
        _preProcessReg(regular);
#ifdef DEBUG_SHOW
        std::cout << "Process result:" << std::endl;
        for (const auto& e : _regular_list) std::cout << e << std::endl;
#endif
        return _regular_list;
    }

private:
    void _preProcessReg(const std::string& reg) 
    {
        std::string content = reg;
        auto point = content.find('\n');
        while (point != std::string::npos) {
            std::string expanded = _extractMarco(content.substr(0, point));
            _regular_list.push_back(_eraseSpace(expanded));
            content = content.substr(point + 1);
            point = content.find('\n');
        }
        log::logMessage(log::LOG_LEVEL::_INFO, "pre-process regular expression success.");
    }

    std::string _eraseSpace(const std::string& str) const
    {
        std::string res;
        for (const auto& e : str) {
            if (e == ' ') continue;
            res += e;
        }
        return res;
    }

    void _macroDefine(const std::string& macrostr)
    {
        std::string key, val, temp;
        bool define_key = true, defining = false;
        for (const auto& e : macrostr) {
            if (e == ' ') {
                if (defining) {
                    if (define_key) {
                        key = temp;
                        define_key = !define_key;
                    } else {
                        val = temp;
                    }
                    defining = !defining;
                    temp.clear();
                }
                continue;
            } else if (e == '\n') {
                if (!define_key && val.empty()) {
                    val = temp;
                    define_key = !define_key;
                }
                if (key.empty() || val.empty()) {
                    if (!key.empty()) log::logMessage(log::LOG_LEVEL::_ERROR, "Macro define not complete.");
                    continue;
                }
                _macro_map[key] = val;
                key.clear();
                val.clear();
                temp.clear();
                define_key = true;
                defining = false;
            } else {
                defining = true;
                temp += e;
            }
        }
        log::logMessage(log::LOG_LEVEL::_INFO, "Macro define success.");
    }

    std::string _expandMacro(const std::string& macrokey) 
    {
        if (_macro_map.find(macrokey) != _macro_map.end()) return "(" + _macro_map[macrokey] + ")";
        log::logMessage(log::LOG_LEVEL::_ERROR, "macro error:%s", macrokey.c_str());
        exit(2);
    }

    std::string _extractMarco(const std::string& macrocontent) 
    {
        std::string res = macrocontent;
        std::string content;
        int begin = res.find('{');
        while (begin != std::string::npos) {
            int end = res.find('}');
            if (end == std::string::npos) {
                log::logMessage(log::LOG_LEVEL::_FATAL, "expression error");
                exit(1);
            }
            if (begin > 0) content += res.substr(0, begin);
            content += _expandMacro(res.substr(begin + 1, end - begin - 1));
            content += res.substr(end + 1);
            res = content;
            content.clear();
            begin = res.find('{');
        }
        return res;
    }

    std::unordered_map<std::string, std::string> _macro_map;
    std::vector<std::string> _regular_list;
};
